
#ifndef _MATRIX_HPP_
#define _MATRIX_HPP_
#include <cstring>
#include <cassert>
#include <iostream>
#include "mrandom.hpp"
template <typename T>
class Matrix
{
  public:
    Matrix() = default;
    Matrix(int row, int col) noexcept
        : r{row}, c{col}, elem{new T[r * c]} {}
    Matrix(int size) noexcept
        : r{size}, c{size}, elem{new T[r * c]} {}
    Matrix(const Matrix &src) noexcept;
    Matrix &operator=(const Matrix &src) noexcept;
    Matrix(Matrix &&src) noexcept;
    Matrix &operator=(Matrix &&src) noexcept;

    T *data() noexcept { return elem; }
    const T *data() const noexcept { return elem; }
    int row() noexcept { return r; }
    const int row() const noexcept { return r; }
    int column() noexcept { return c; }
    const int column() const noexcept { return c; }

    bool operator==(const Matrix &a) const noexcept;
    T& operator()(int i, int j) noexcept {return elem[i*c + j];}
    Matrix& operator+=(const Matrix& a) noexcept;
    Matrix operator+(const Matrix& b) noexcept;

    Matrix &initElem() noexcept;
    Matrix &SetToValue(T value) noexcept;
    Matrix& InitRandom(T lo, T hi) noexcept;
    void print() noexcept;
    // 主要目的为允许定义不指定大小，到后面再从新分配空间
    Matrix &reallocate(int rol, int col) noexcept;
    Matrix &reallocate(int size) noexcept;
    Matrix multiply1(const Matrix &matB) const noexcept;
    Matrix multiply2(const Matrix& matB) const noexcept;
    bool is_equal(const Matrix &matB) const noexcept;

  private:
    int r{}, c{};
    T *elem{};
    inline void copy_from(const Matrix &src) noexcept;
    inline void move_from(Matrix &src) noexcept;
};

template <typename T>
void Matrix<T>::copy_from(const Matrix &src) noexcept
{
    r = src.r;
    c = src.c;
    elem = new T[r * c];
    memcpy(elem, src.elem, sizeof(T) * r * c);
}

template <typename T>
void Matrix<T>::move_from(Matrix &src) noexcept
{
    r = src.r;
    c = src.c;
    elem = src.elem;
    src.elem = nullptr;
    src.r = src.c = 0;
}

template <typename T>
Matrix<T>::Matrix(const Matrix &src) noexcept
{
    copy_from(src);
}

template <typename T>
Matrix<T> &Matrix<T>::operator=(const Matrix &src) noexcept
{
    if (this == &src)
        return *this;
    delete[] elem;
    copy_from(src);
    return *this;
}

template <typename T>
Matrix<T>::Matrix(Matrix &&src) noexcept
{
    move_from(src);
}
template <typename T>
Matrix<T> &Matrix<T>::operator=(Matrix &&src) noexcept
{
    // 没检查自移动
    delete[] elem;
    move_from(src);
    return *this;
}

template <typename T>
Matrix<T> &Matrix<T>::initElem() noexcept
{
    assert(elem != nullptr);
    for (int i = 0; i < r; ++i)
        for (int j = 0; j < c; ++j)
            elem[i * c + j] = i*c + j;
    return *this;
}

template <typename T>
void Matrix<T>::print() noexcept
{
    assert(elem != nullptr);
    for (int i = 0; i < r; ++i)
    {
        for (int j = 0; j < c; ++j)
        {
            std::cout << elem[i * c + j] << "\t";
        }
        std::cout << "\n";
    }
}

template <typename T>
Matrix<T> &Matrix<T>::reallocate(int row, int col) noexcept
{
    delete[] elem;
    r = row;
    c = col;
    elem = new T[r * c];
    return *this;
}

template <typename T>
Matrix<T> &Matrix<T>::reallocate(int size) noexcept
{
    delete[] elem;
    r = c = size;
    elem = new T[r * c];
    return *this;
}

template <typename T>
Matrix<T> Matrix<T>::multiply1(const Matrix &matB) const noexcept
{
    if (c != matB.r)
    {
        std::cerr << "error: mata.column != matb.row\n";
        exit(EXIT_FAILURE);
    }
    int Bc = matB.c;
    Matrix mat(r, Bc);
    T *matC{mat.elem};
    T *elemB = matB.elem;
    T tmp{};
    for (int m = 0; m < r; ++m)
    {
        for (int n = 0; n < Bc; ++n)
        {
            for (int p = 0; p < c; ++p)
            {
                // tmp += elem[m * c + p] * matB.elem[p * Bc + n];
                tmp += elem[m * c + p] * elemB[p * Bc + n];
                // tmp += matA[m*pdim + p] * matB[p*ndim+n];
            }
            matC[m * Bc + n] = tmp;
            tmp = 0.0;
        }
    }
    return std::move(mat);
}

template <typename T>
bool Matrix<T>::operator==(const Matrix &a) const noexcept
{
    if ((r != a.r) || (c != a.c))
        return false;
    T *elemB{a.elem};
    for (int i = 0; i < r; ++i)
    {
        for (int j = 0; j < c; ++j)
        {
            if (elem[i * c + j] != elemB[i * c + j])
            return false;
        }
    }
    return true;
}

template <typename T>
bool Matrix<T>::is_equal(const Matrix &matB) const noexcept
{
    return *this == matB;
}

template<typename T> 
Matrix<T> Matrix<T>::multiply2(const Matrix &matB) const noexcept
{
    if (c != matB.r)
    {
        std::cerr << "error: mata.column != matb.row\n";
        exit(EXIT_FAILURE);
    }
    int Bc = matB.c;
    Matrix mat(r, Bc);
    T *matC{mat.elem}, *elemB = matB.elem;

    for(int i = 0; i < r; ++i)
    {
        for(int j = 0; j < Bc; ++j)
        {
            matC[i*Bc + j] = 0.0;
        }
    }

    for(int m = 0; m < r; ++m)
    {
        for(int p = 0; p < c; ++p)
        {
            for(int n = 0; n < Bc; ++n)
            {
                matC[m*Bc + n] += elem[m*c + p]*elemB[p*Bc + n];
            }
        }
    }
    return std::move(mat);
}

template<typename T> 
Matrix<T>& Matrix<T>::SetToValue(T value) noexcept
{
    assert(elem != nullptr);
    for(int i = 0; i < r; ++i)
        for(int j = 0; j < c; ++j)
            elem[i*c + j] = value;   
    return *this; 
}

template<typename T> 
Matrix<T>& Matrix<T>::InitRandom(T lo, T hi) noexcept
{
    assert(elem != nullptr);
    int ll = lo, hh = hi;
    Rand_int rom{ll, hh};
    for (int i = 0; i < r; ++i)
        for (int j = 0; j < c; ++j)
            elem[i * c + j] = rom();
    return *this;
}

template<typename T> 
Matrix<T>& Matrix<T>::operator+=(const Matrix& a) noexcept
{
    assert(elem != nullptr && c == a.c && r == a.r);
    double *aelem = a.elem;
    for(int i = 0; i < r; ++i)
    {
        for(int j = 0; j < c; ++j)
            elem[i*c + j] += aelem[i*c + j];
    }
    return *this;
}

template<typename T> 
Matrix<T> Matrix<T>::operator+(const Matrix<T>& a) noexcept
{
    assert(elem != nullptr && c == a.c && r == a.r);
    Matrix d(r, c);
    double *aelem = a.elem;
    double *delem = d.elem;

    for(int i = 0; i < r; ++i)
    {
        for(int j = 0; j < c; ++j)
            delem[i*c + j] = elem[i*c + j] + aelem[i*c + j];
    }
    return std::move(d);
}


#endif